Assuming you are familiar with C#;
If I give you a Type
and tell you to create an object with it, you would automatically think of Activator.CreateInstance
right?
What if I tell you that instanciating a Type using Expression Trees is much faster?
The code for the benchmarks is in this repository.
Background
I am currently in an internship and the project I’m working on needs to be as extensible as possible.
Meaning that users/developers should have the possiblity to add new functionalities (or at least a variant of a functionality) easily.
For instance, I have a Manager
class and a couple of default classes that inherit implement, each of them does something specific.
Now the idea is to make it possible for people to plug-in their SomethingManager
class.
I ended up with a list of Type
that inherit from Manager
. Now all I have to do is instanciate and execute these types when I need them. Like most C# programmers, I immediately thought of Activator.CreateInstance
!
However, there must be a faster way for instanciating a type right?
Instaciating a type - some of the ways
Let’s imagine that our base class is TestClass
.
Activator
[Benchmark]
public TestClass Activator()
{
return (TestClass)System.Activator.CreateInstance(_type);
}
ConstructorInfo
(Reflection)
// This line should only be ran once
var _constructor = _type.GetConstructor(Type.EmptyTypes);
[Benchmark]
public TestClass Constructor()
{
return (TestClass)_constructor.Invoke(null);
}
Expression Trees
// This line should only be ran once
var _delegate = Expression.Lambda(Expression.New(_type)).Compile();
[Benchmark]
public TestClass Delegate()
{
return (TestClass)_delegate.DynamicInvoke();
}
Func<object>
(Expression Trees)
// This line should only be ran once
var _func = Expression.Lambda<Func<object>>(Expression.New(_type)).Compile();
[Benchmark]
public TestClass Func()
{
return (TestClass)_func();
}
Func<TestClass>
(Expression Trees)
// This line should only be ran once
var _typedFunc = Expression.Lambda<Func<TestClass>>(Expression.New(_type)).Compile();
[Benchmark]
public TestClass TypedFunc()
{
return _typedFunc();
}
Results
I added the new TestClass()
benchmark as a baseline.
As you can see, Expression Trees compiled Func<object>
or Func<TestClass>
are so close to being as fast as the baseline.
On the other hand, Expression Trees compiled to a plain Delegate
and called using DynamicInvoke()
are extremely slow.
This is due to the fact that a Delegate
is dynamically invoked, .net has to use Reflection to figure out the Type and other informations, and this happens everytime.
For more informations, check this Stackoverflow question.
Raw results
Method | Job | Mean | Error | StdDev | Ratio | RatioSD | Rank |
---|---|---|---|---|---|---|---|
New | Clr | 3.722 ns | 0.0581 ns | 0.0544 ns | 1.00 | 0.00 | 2 |
Activator | Clr | 48.439 ns | 0.1852 ns | 0.1733 ns | 13.02 | 0.20 | 9 |
Constructor | Clr | 137.333 ns | 0.3617 ns | 0.3383 ns | 36.91 | 0.54 | 11 |
Delegate | Clr | 727.431 ns | 1.6431 ns | 1.2829 ns | 195.50 | 3.15 | 17 |
Func | Clr | 11.439 ns | 0.0742 ns | 0.0658 ns | 3.07 | 0.04 | 7 |
TypedFunc | Clr | 10.749 ns | 0.0752 ns | 0.0703 ns | 2.89 | 0.04 | 6 |
New | Core | 3.973 ns | 0.0417 ns | 0.0370 ns | 1.07 | 0.02 | 3 |
Activator | Core | 43.673 ns | 0.1425 ns | 0.1333 ns | 11.74 | 0.16 | 8 |
Constructor | Core | 96.602 ns | 0.3215 ns | 0.3007 ns | 25.96 | 0.39 | 10 |
Delegate | Core | 524.034 ns | 1.1478 ns | 1.0736 ns | 140.84 | 2.10 | 16 |
Func | Core | 6.282 ns | 0.2092 ns | 0.3195 ns | 1.72 | 0.09 | 5 |
TypedFunc | Core | 4.538 ns | 0.0891 ns | 0.0833 ns | 1.22 | 0.02 | 4 |
New | CoreRT | 3.187 ns | 0.0759 ns | 0.0710 ns | 0.86 | 0.02 | 1 |
Activator | CoreRT | 328.310 ns | 1.3142 ns | 1.2293 ns | 88.23 | 1.24 | 15 |
Constructor | CoreRT | 142.282 ns | 0.7171 ns | 0.6708 ns | 38.24 | 0.55 | 12 |
Delegate | CoreRT | 298.431 ns | 2.0301 ns | 1.8990 ns | 80.21 | 1.36 | 14 |
Func | CoreRT | 220.747 ns | 0.5149 ns | 0.4816 ns | 59.33 | 0.84 | 13 |
TypedFunc | CoreRT | 219.806 ns | 1.5278 ns | 1.4291 ns | 59.08 | 1.02 | 13 |
To sum up, instanciating a type at runtime can be done in multiple ways.
The fasted way (in these benchmarks) is using Expression Trees to generate a typed Func<X>
and invoke it when needed.
For the future, I should also benchmark running IL Code directly.